import rescuecore.*;
import rescuecore.objects.*;
import rescuecore.event.*;
import java.util.*;

public class Bomberman extends PlatoonAgent {

	private int commId;
	private static int nextCommId=0;
	private String messageToTell=""; 
	private int oldPosition = 0; 
	private int oldPositionExtra = 0; 
	private int stoppedCount =0;
    /**
       Lista de targets (Buildings em incêndio percebidos pelo agente)
    */
    private List targets;
    private List storedRoads;
    private List globalTargets;
	
    /**
       Constroi um FireBrigade Agent
       Nota: observe o parâmetro passado ao construtor da superclasse, que determina o TIPO do agente (FireBrigade)
    */
    public Bomberman() {
		super(RescueConstants.AGENT_TYPE_FIRE_BRIGADE); 
		targets = new ArrayList();
		storedRoads = new ArrayList();	
		globalTargets = new ArrayList();
    }

    /**
	Retorna uma referência de mim mesmo no mundo (necessário pelo simulador)       
	@return the FireBrigade controlled by this agent
    */
    private FireBrigade me() {
		return (FireBrigade)memory.lookup(id);
    }

    /**
      * Inicialização do agente:
	    Nesta inicialização é criado um Listener ("callback") que será notificado sempre
        que um novo objeto do tipo Building for adicionado ou modificado na memória (percepção) do agente. 
      */
    public void initialise(RescueObject[] knowledge, RescueObject self) {
		
		super.initialise(knowledge,self);
		commId = ++nextCommId;
		//System.out.println("BomberMan commId: "	+ commId);
	
		memory.addMemoryListener(new MemoryListener() {
				public void objectAdded(ObjectAddedEvent event) {
					RescueObject o = event.getObject();
					if (o.isBuilding()) {
						if (((Building)o).isOnFire()) {
							if(!targets.contains(o)) {
								targets.add(o); 
							}
							messageToTell = new String (messageToTell + "B=" + ((Building) o).getID() + ";") ;
						}
					}
					else if (o.isHumanoid()) {
						Humanoid h = (Humanoid)o;
						if ((h.isBuried() || h.isDamaged()) && h.isAlive() && memory.lookup(h.getPosition())!=null) {
							messageToTell = new String ("V=" + h.getPosition() + ";" + messageToTell);
						}
					}
					else if (o.isRoad()) {
						if (((Road) o).isBlocked() && !((Road) o).hasFreeLines()) {
							if(!storedRoads.contains(o)){
								storedRoads.add(o);
							}
							messageToTell = new String (messageToTell + "R=" + ((Road)o).getID() + ";");
						}
					}
				}
				public void objectChanged(ObjectChangedEvent event) {
					RescueObject o = event.getObject();
					if (o.isBuilding() && event.getProperty()==RescueConstants.PROPERTY_FIERYNESS) { // We only care about the fieryness of the building - we can ignore all other updates
						if (((Building)o).isOnFire()){
							if(!targets.contains(o)) {
								targets.add(o); 
							}
							messageToTell = new String (messageToTell + "B=" + ((Building) o).getID() + ";") ;
						}
						else targets.remove(o); // Otherwise remove it from the target list
					}
				}
			});
    }

    public void sense() {
		boolean wasSomethingDecided = false;

        // se jah estiver num refugio reabastecendo, e enquanto não estiver
        // com o tanque cheio, não faz
        if (getLocation().isRefuge() && me().getWaterQuantity()<RescueConstants.MAX_WATER) {
			wasSomethingDecided = true;
		}


        // caso terminar a agua, precisa recarregar num refugio proximo
        if (me().getWaterQuantity()==0) {
			wasSomethingDecided = moveToClosestRefuge();
		}


        // Ordenar os targets por proximidade
        if(!wasSomethingDecided && !targets.isEmpty()) SampleSearch.sortByDistance(targets, me(), memory);

        // Verifica se posso combater algum dos targets
        Iterator it = targets.iterator();
        while(it.hasNext() && !wasSomethingDecided ){
            Building b = (Building)it.next();
            // confirmar se está realmente em chamas
            if (b.isOnFire()){

				wasSomethingDecided = tryToExtinguish(b);

				if(!wasSomethingDecided) 
					wasSomethingDecided = moveToAnyEntrance(b);

            }else{ // se nao estiver em chamas, ignorar e analisar o proximo target
                it.remove();
                continue;
            }
        }

		if(timeStep % nextCommId == (commId-1) && messageToTell.length() > 0){
			tell(messageToTell.getBytes());
			//System.out.println( me() + " at step " + timeStep + ", telling: " + messageToTell);
			messageToTell = "";
		}


		if(getLocation().isRoad()) {
			Road road = (Road) getLocation();
			if(road.isBlocked() && !road.hasFreeLines() && getPosition() == oldPosition && oldPositionExtra == getPositionExtra()){
				if(++stoppedCount > 5) wasSomethingDecided = false;
			}
			else stoppedCount = 0;
		}

		oldPosition = getPosition();
		oldPositionExtra = getPositionExtra();

		if(!wasSomethingDecided) moveToRandomTarget();

    }

	private boolean tryToExtinguish(Building b){
		//  verificar se está ao alcance do jato d'agua
		try{
			//say(tellMsg.getBytes());
			if (memory.getDistance(me(), b) < RescueConstants.MAX_EXTINGUISH_DISTANCE) {

				// apagar (jogar água)
				extinguish(b);
				return true;
			}
			
		}catch(CannotFindLocationException e){
			System.err.println("ERRO, nao consegui calcular distancia!");
			System.err.println(e);
		}

		return false;
	}


     /**
      * Detecta e move para o refugio mais proximo
      */
     private boolean moveToClosestRefuge() {
		// proximas 3 linhas transforma uma Collection de Refuge em um Array de Refuge p/ passar p/ sort
        Collection<RescueObject> refugiosCollection = memory.getObjectsOfType(RescueConstants.TYPE_REFUGE);
		Refuge[] refugios = new Refuge[refugiosCollection.size()];
		refugiosCollection.toArray(refugios);
        SampleSearch.sortByDistance(refugios,me(),memory);
        // verifica qual deles eh possivel calcular rota...
		for (int i=0;i<refugios.length;++i) {
			Refuge r = (Refuge)refugios[i];
			int[] path = SampleSearch.breadthFirstSearch(getLocation(),r,memory);
			if (path!=null) {
				move(path);
				return true;
			}
		}
		return false;
    }


	private boolean moveToRandomTarget() {
        // obter todas as ruas
		Object[] roads =  memory.getObjectsOfType(RescueConstants.TYPE_ROAD).toArray();
		// sortear randomicamente uma delas
        Road target = (Road)roads[(int)(Math.random()*roads.length)]; 
        // computar rota 
		int[] path = SampleSearch.breadthFirstSearch(getLocation(),target,memory);
		if (path!=null) {
			move(path); 
			return true;
		}
		return false;
	}


	private boolean moveToAnyEntrance(Building b){
		// próximo (= entrada do Building)
		int [] entrances = b.getEntrances();
		Node entrance = (Node)memory.lookup(entrances[(int)(Math.random()*entrances.length)]); // uma entrada qualquer do Building
		// computar rota
		int[] path = SampleSearch.breadthFirstSearch(getLocation(),entrance,memory); 
		// se existe rota
		if (path!=null) {
			// mover para o target selecionado
			move(path);
			return true;
		}

		return false;
	}



	public boolean willListenHearTell(int from) {
		if(from == id) return false;
		else return true;
	}
	

	public boolean willListenHearSay(int from) {
		if(from == id) return false;
		else return true;
	}


	protected void parserBuilding(String msg){
		String [] result = msg.split(";");
		String received = new String();

		for (int i=0; i<result.length; i++){
			if(result[i].split("=")[0].startsWith("B")) {
				Object o = memory.lookup( (new  Integer(result[i].split("=")[1])).intValue() );
				if(!targets.contains(o)){
					targets.add((Building) o);
					globalTargets.add(new  Integer(result[i].split("=")[1]));
					received = received + result[i].split("=")[1] + ";";
				}
			}
		}

		if(received.length() > 0) System.out.println("[" + me() + "] Team Tasks received: " + received );
		
	}


	protected boolean hearTell(int from, byte[] msg){
		if(from != id ) parserBuilding(new String(msg));

		//System.out.println("[" + me() + "] "  + new String(msg) );
		return true;
	}

	protected boolean hearSay(int from, byte [] msg){
		//System.out.println("Bomberman escutou " + new String(msg) + " de " + from);
		return true;
	}

	
}
